<!DOCTYPE html>
<html>
<head>
<title>Regrouping Demo</title>
<meta name="description" content="Create new collapsible groups, ungroup existing groups, and use drag-and-drop to change grouping memberships." />
<!-- Copyright 1998-2016 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<link href="../assets/css/goSamples.css" rel="stylesheet" type="text/css" />  <!-- you don't need to use this -->
<script src="goSamples.js"></script>  <!-- this is only for the GoJS Samples framework -->
<script id="code">
  function init() {
    if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this
    var $ = go.GraphObject.make;

    myDiagram =
      $(go.Diagram, "myDiagramDiv",
        {
          allowDrop: true, // from Palette
          // what to do when a drag-drop occurs in the Diagram's background
          mouseDrop: function(e) { finishDrop(e, null); },
          layout:  // Diagram has simple horizontal layout
            $(go.GridLayout,
              { wrappingWidth: Infinity, alignment: go.GridLayout.Position, cellSize: new go.Size(1, 1) }),
          initialContentAlignment: go.Spot.Center,
          "commandHandler.archetypeGroupData": { isGroup: true, category: "OfNodes" },
          "undoManager.isEnabled": true
        });

    // There are two templates for Groups, "OfGroups" and "OfNodes".

    // this function is used to highlight a Group that the selection may be dropped into
    function highlightGroup(e, grp, show) {
      if (!grp) return;
      e.handled = true;
      if (show) {
        // cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
        // instead depend on the DraggingTool.draggedParts or .copiedParts
        var tool = grp.diagram.toolManager.draggingTool;
        var map = tool.draggedParts || tool.copiedParts;  // this is a Map
        // now we can check to see if the Group will accept membership of the dragged Parts
        if (grp.canAddMembers(map.toKeySet())) {
          grp.isHighlighted = true;
          return;
        }
      }
      grp.isHighlighted = false;
    }

    // Upon a drop onto a Group, we try to add the selection as members of the Group.
    // Upon a drop onto the background, or onto a top-level Node, make selection top-level.
    // If this is OK, we're done; otherwise we cancel the operation to rollback everything.
    function finishDrop(e, grp) {
      var ok = (grp !== null
                ? grp.addMembers(grp.diagram.selection, true)
                : e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
      if (!ok) e.diagram.currentTool.doCancel();
    }

    myDiagram.groupTemplateMap.add("OfGroups",
      $(go.Group, "Auto",
        {
          background: "transparent",
          // highlight when dragging into the Group
          mouseDragEnter: function(e, grp, prev) { highlightGroup(e, grp, true); },
          mouseDragLeave: function(e, grp, next) { highlightGroup(e, grp, false); },
          computesBoundsAfterDrag: true,
          // when the selection is dropped into a Group, add the selected Parts into that Group;
          // if it fails, cancel the tool, rolling back any changes
          mouseDrop: finishDrop,
          handlesDragDropForMembers: true,  // don't need to define handlers on member Nodes and Links
          // Groups containing Groups lay out their members horizontally
          layout:
            $(go.GridLayout,
              { wrappingWidth: Infinity, alignment: go.GridLayout.Position,
                  cellSize: new go.Size(1, 1), spacing: new go.Size(4, 4) })
        },
        new go.Binding("background", "isHighlighted", function(h) { return h ? "rgba(255,0,0,0.2)" : "transparent"; }).ofObject(),
        $(go.Shape, "Rectangle",
          { fill: null, stroke: "#FFDD33", strokeWidth: 2 }),
        $(go.Panel, "Vertical",  // title above Placeholder
          $(go.Panel, "Horizontal",  // button next to TextBlock
            { stretch: go.GraphObject.Horizontal, background: "#FFDD33" },
            $("SubGraphExpanderButton",
              { alignment: go.Spot.Right, margin: 5 }),
            $(go.TextBlock,
              {
                alignment: go.Spot.Left,
                editable: true,
                margin: 5,
                font: "bold 18px sans-serif",
                opacity: 0.75,
                stroke: "#404040"
              },
              new go.Binding("text", "text").makeTwoWay())
          ),  // end Horizontal Panel
          $(go.Placeholder,
            { padding: 5, alignment: go.Spot.TopLeft })
        )  // end Vertical Panel
      ));  // end Group and call to add to template Map

    myDiagram.groupTemplateMap.add("OfNodes",
      $(go.Group, "Auto",
        {
          background: "transparent",
          ungroupable: true,
          // highlight when dragging into the Group
          mouseDragEnter: function(e, grp, prev) { highlightGroup(e, grp, true); },
          mouseDragLeave: function(e, grp, next) { highlightGroup(e, grp, false); },
          computesBoundsAfterDrag: true,
          // when the selection is dropped into a Group, add the selected Parts into that Group;
          // if it fails, cancel the tool, rolling back any changes
          mouseDrop: finishDrop,
          handlesDragDropForMembers: true,  // don't need to define handlers on member Nodes and Links
          // Groups containing Nodes lay out their members vertically
          layout:
            $(go.GridLayout,
              { wrappingColumn: 1, alignment: go.GridLayout.Position,
                  cellSize: new go.Size(1, 1), spacing: new go.Size(4, 4) })
        },
        new go.Binding("background", "isHighlighted", function(h) { return h ? "rgba(255,0,0,0.2)" : "transparent"; }).ofObject(),
        $(go.Shape, "Rectangle",
          { fill: null, stroke: "#33D3E5", strokeWidth: 2 }),
        $(go.Panel, "Vertical",  // title above Placeholder
          $(go.Panel, "Horizontal",  // button next to TextBlock
            { stretch: go.GraphObject.Horizontal, background: "#33D3E5" },
            $("SubGraphExpanderButton",
              { alignment: go.Spot.Right, margin: 5 }),
            $(go.TextBlock,
              {
                alignment: go.Spot.Left,
                editable: true,
                margin: 5,
                font: "bold 16px sans-serif",
                opacity: 0.75,
                stroke: "#404040"
              },
              new go.Binding("text", "text").makeTwoWay())
          ),  // end Horizontal Panel
          $(go.Placeholder,
            { padding: 5, alignment: go.Spot.TopLeft })
        )  // end Vertical Panel
      ));  // end Group and call to add to template Map

    myDiagram.nodeTemplate =
      $(go.Node, "Auto",
        { // dropping on a Node is the same as dropping on its containing Group, even if it's top-level
          mouseDrop: function(e, nod) { finishDrop(e, nod.containingGroup); }
        },
        $(go.Shape, "Rectangle",
          { fill: "#ACE600", stroke: null },
          new go.Binding("fill", "color")),
        $(go.TextBlock,
          {
            margin: 5,
            editable: true,
            font: "bold 13px sans-serif",
            opacity: 0.75,
            stroke: "#404040"
          },
          new go.Binding("text", "text").makeTwoWay())
      );

    // initialize the Palette and its contents
    myPalette =
      $(go.Palette, "myPaletteDiv",
        {
          nodeTemplateMap: myDiagram.nodeTemplateMap,
          groupTemplateMap: myDiagram.groupTemplateMap,
          layout: $(go.GridLayout, { wrappingColumn: 1, alignment: go.GridLayout.Position })
        });
    myPalette.model = new go.GraphLinksModel([
      { text: "lightgreen", color: "#ACE600" },
      { text: "yellow", color: "#FFDD33" },
      { text: "lightblue", color: "#33D3E5" }
    ]);

    var slider = document.getElementById("levelSlider");
    slider.addEventListener('change', reexpand);
    slider.addEventListener('input', reexpand);

    load();
  }

  function expandGroups(g, i, level) {
    if (!(g instanceof go.Group)) return;
    g.isSubGraphExpanded = i < level;
    g.memberParts.each(function(m) {
      expandGroups(m, i + 1, level);
    })
  }
  function reexpand(e) {
    myDiagram.startTransaction("reexpand");
    var level = parseInt(document.getElementById("levelSlider").value);
    myDiagram.findTopLevelGroups().each(function(g) { expandGroups(g, 0, level); })
    myDiagram.commitTransaction("reexpand");
  }

  // save a model to and load a model from JSON text, displayed below the Diagram
  function save() {
    document.getElementById("mySavedModel").value = myDiagram.model.toJson();
    myDiagram.isModified = false;
  }
  function load() {
    myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
  }
</script>
</head>
<body onload="init()">
<div id="sample">
  <div style="width:100%; white-space:nowrap;">
    <span style="display: inline-block; vertical-align: top; padding: 5px; width:100px">
      <div id="myPaletteDiv" style="border: solid 1px black; height: 500px"></div>
    </span>
    <span style="display: inline-block; vertical-align: top; padding: 5px; width:80%">
      <div id="myDiagramDiv" style="border: solid 1px black; height: 500px"></div>
    </span>
  </div>
  <p>
    This sample allows the user to drag nodes, including groups, into and out of groups,
    both from the Palette as well as from within the Diagram.
    See the <a href="../intro/groups.html">Groups Intro page</a> for an explanation of GoJS Groups.
  </p>
  <p>
    Highlighting to show feedback about potential addition to a group during a drag is implemented
    using <a>GraphObject.mouseDragEnter</a> and <a>GraphObject.mouseDragLeave</a> event handlers.
    Because <a>Group.computesBoundsAfterDrag</a> is true, the Group's <a>Placeholder</a>'s bounds are
    not computed until the drop happens, so the group does not continuously expand as the user drags
    a member of a group.
  </p>
  <p>
    When a drop occurs on a Group or a regular Node, the <a>GraphObject.mouseDrop</a> event handler
    adds the selection (the dragged Nodes) to the Group as new members.
  </p>
  <p>
    The <a>TextBlock</a>s use the <a>GraphObject.opacity</a> property, which allows for the text
    to somewhat match the color of what it's on top of.
  </p>
  <p>
    The slider controls how many nested levels of Groups are expanded. </br>
    Semantic zoom level: <input id="levelSlider" type="range" min="0" max="5" value="3" />
  </p>
  <div id="buttons">
    <button id="saveModel" onclick="save()">Save</button>
    <button id="loadModel" onclick="load()">Load</button>
    Diagram Model saved in JSON format:
  </div>
  <textarea id="mySavedModel" style="width:100%;height:300px">
{ "class": "go.GraphLinksModel",
  "nodeDataArray": [
{"key":1, "text":"Main 1", "isGroup":true, "category":"OfGroups"},
{"key":2, "text":"Main 2", "isGroup":true, "category":"OfGroups"},
{"key":3, "text":"Group A", "isGroup":true, "category":"OfNodes", "group":1},
{"key":4, "text":"Group B", "isGroup":true, "category":"OfNodes", "group":1},
{"key":5, "text":"Group C", "isGroup":true, "category":"OfNodes", "group":2},
{"key":6, "text":"Group D", "isGroup":true, "category":"OfNodes", "group":2},
{"key":7, "text":"Group E", "isGroup":true, "category":"OfNodes", "group":6},
{"text":"first A", "group":3, "key":-7},
{"text":"second A", "group":3, "key":-8},
{"text":"first B", "group":4, "key":-9},
{"text":"second B", "group":4, "key":-10},
{"text":"third B", "group":4, "key":-11},
{"text":"first C", "group":5, "key":-12},
{"text":"second C", "group":5, "key":-13},
{"text":"first D", "group":6, "key":-14},
{"text":"first E", "group":7, "key":-15}
 ],
  "linkDataArray": [  ]}
  </textarea>
</div>
</body>
</html>
